package com.bsj.travel.openapi.service.impl;

import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.bsj.travel.cached.redis.RedisCached;
import com.bsj.travel.config.rocketmq.producer.BaseMqPushUtil;
import com.bsj.travel.constant.*;
import com.bsj.travel.def.common.DO.DeviceDO;
import com.bsj.travel.def.common.DO.ProductDO;
import com.bsj.travel.def.common.DO.ProductTopicDO;
import com.bsj.travel.def.common.DTO.mqtt.MqttEventPublish;
import com.bsj.travel.def.common.DTO.web.device.DeviceSearchDTO;
import com.bsj.travel.def.common.DTO.web.openApi.device.DeviceOpenApiDTO;
import com.bsj.travel.def.common.DTO.web.openApi.device.DeviceOpenApiMsgReportDTO;
import com.bsj.travel.def.common.DTO.web.openApi.device.DeviceOpenApiParamDTO;
import com.bsj.travel.def.common.VO.device.DeviceCorrelationInfoVO;
import com.bsj.travel.def.common.VO.device.DeviceInfoVO;
import com.bsj.travel.def.common.VO.device.DeviceTemplateVO;
import com.bsj.travel.def.global.JsonResult;
import com.bsj.travel.def.global.JsonResultEnum;
import com.bsj.travel.def.global.mq.Rp;
import com.bsj.travel.exception.ApiException;
import com.bsj.travel.mapper.mysql.DeviceMapper;
import com.bsj.travel.mapper.mysql.ProductTopicMapper;
import com.bsj.travel.openapi.service.DeviceOpenApiService;
import com.bsj.travel.security.auth.AuthProduct;
import com.bsj.travel.util.DateUtils;
import com.bsj.travel.util.encipher.DeviceEncryptUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author LiJiaXin
 * @version 1.0.0
 * @description DeviceOpenApiServiceImpl
 * @time 2024/4/7 14:43
 */
@Slf4j
@Service
public class DeviceOpenApiServiceImpl implements DeviceOpenApiService {

    @Resource
    private DeviceMapper deviceMapper;

    @Resource
    private ProductTopicMapper productTopicMapper;

    @Autowired
    private RedisCached redisCached;

    @Resource
    private AuthProduct authProduct;

    @Autowired
    private BaseMqPushUtil baseMqPushUtil;

    /**
     * 添加设备
     *
     * @param deviceOpenApiDTO
     * @return com.bsj.travel.def.global.JsonResult
     * @author ljx
     * @time 2024/4/7 14:47
     */
    @Override
    @Transactional
    public JsonResult addDevice(DeviceOpenApiDTO deviceOpenApiDTO) {
        paramCheck(deviceOpenApiDTO);
        DeviceDO deviceDO = new DeviceDO();
        BeanUtils.copyProperties(deviceOpenApiDTO, deviceDO);
        String productKey = deviceOpenApiDTO.getProductKey();
        deviceDO.setTableName(Constants.DEVICE_TAB_STR + productKey);
        deviceDO.setDeviceSecret(DeviceEncryptUtil.EncryptDevSecret(productKey, deviceOpenApiDTO.getDeviceName()));
        deviceDO.setTerminalModel(TerminalModelConstants.DEVICE_DIRECT_CONNECTION);
        if (deviceMapper.insert(deviceDO) > NumericalConstants.ZERO) {
            redisCached.hsetDeviceInfo(productKey, deviceDO);
            sent(deviceDO, productKey);
            log.info("设备信息{}", deviceDO);
            return JsonResult.success(deviceDO);
        } else {
            return JsonResult.fail();
        }
    }

    /**
     * 批量添加设备
     *
     * @param deviceOpenApiParamDTO
     * @return com.bsj.travel.def.global.JsonResult
     * @author ljx
     * @time 2024/4/8 11:16
     */
    @Override
    @Transactional
    public JsonResult batchAddDevice(DeviceOpenApiParamDTO deviceOpenApiParamDTO) {
        List<DeviceOpenApiDTO> devices = deviceOpenApiParamDTO.getDevices();
        String productKey = deviceOpenApiParamDTO.getProductKey();
        if (devices.size() > NumericalConstants.TEN_THOUSAND) {
            return JsonResult.fail(JsonResultEnum.FILE_MODEL_BIG);
        }
        //校验文件数据
        String tableName = Constants.DEVICE_TAB_STR + productKey;
        String errorInfo = checkImportData(tableName, devices);
        if (StringUtils.isNotBlank(errorInfo)) {
            return JsonResult.fail(errorInfo);
        }
        List<DeviceDO> deviceDOS = devices.stream().map(value -> {
            DeviceDO deviceDO = new DeviceDO();
            BeanUtils.copyProperties(value, deviceDO);
            deviceDO.setTerminalModel(TerminalModelConstants.DEVICE_DIRECT_CONNECTION);
            deviceDO.setDeviceSecret(DeviceEncryptUtil.EncryptDevSecret(productKey, value.getDeviceName()));
            return deviceDO;
        }).collect(Collectors.toList());
        deviceMapper.batchAddDevice(tableName, deviceDOS);
        deviceDOS.forEach(deviceDO -> {
            redisCached.hsetDeviceInfo(productKey, deviceDO);
            sent(deviceDO, productKey);
        });
        return JsonResult.success(deviceDOS);
    }

    /**
     * 删除设备
     *
     * @param deviceOpenApiParamDTO
     * @return com.bsj.travel.def.global.JsonResult
     * @author ljx
     * @time 2024/4/8 11:48
     */
    @Override
    @Transactional
    public JsonResult deleteDevice(DeviceOpenApiParamDTO deviceOpenApiParamDTO) {
        String productKey = deviceOpenApiParamDTO.getProductKey();
        List<String> deviceNames = deviceOpenApiParamDTO.getDeviceNames();
        String tableName = Constants.DEVICE_TAB_STR + productKey;
        deviceMapper.deleteByDeviceNames(tableName, deviceNames);
        deviceNames.forEach(deviceName -> {
            DeviceDO deviceDO = redisCached.hgetDeviceInfo(productKey, deviceName);
            if (deviceDO != null) {
                redisCached.hdelDeviceInfo(productKey, deviceName);
                sent(deviceDO, productKey);
            }
        });
        return JsonResult.success();
    }

    /**
     * 获取设备详情
     *
     * @param productKey
     * @param deviceName
     * @return com.bsj.travel.def.global.JsonResult
     * @author ljx
     * @time 2024/4/8 13:57
     */
    @Override
    public DeviceCorrelationInfoVO getByDeviceName(String productKey, String deviceName) {
        String tableName = Constants.DEVICE_TAB_STR + productKey;
        DeviceDO deviceDO = deviceMapper.selectByDeviceName(tableName, deviceName);
        if (deviceDO == null) {
            throw new ApiException(JsonResultEnum.DEVICE_IS_NOT_EXIST);
        }
        Date gmtActive = deviceDO.getGmtActive();
        ProductDO productDO = redisCached.hgetProductInfo(productKey);
        DeviceCorrelationInfoVO deviceCorrelationInfoVO = new DeviceCorrelationInfoVO();
        BeanUtils.copyProperties(deviceDO, deviceCorrelationInfoVO);
        deviceCorrelationInfoVO.setProductName(productDO.getProductName());
        deviceCorrelationInfoVO.setProductKey(productKey);
        deviceCorrelationInfoVO.setNodesType(productDO.getNodesType());
        deviceCorrelationInfoVO.setAuthMethod(productDO.getAuthMethod());
        deviceCorrelationInfoVO.setGmtCreate(DateUtils.parseDateToStr(deviceDO.getGmtCreate()));
        if (gmtActive == null) {
            deviceCorrelationInfoVO.setStatus(NumericalConstants.ONE);
        } else {
            deviceCorrelationInfoVO.setStatus(redisCached.getDeviceStatus(deviceName + "&" + productKey));
            deviceCorrelationInfoVO.setGmtActive(DateUtils.parseDateToStr(gmtActive));
        }
        DeviceDO deviceCache = redisCached.hgetDeviceInfo(productKey, deviceName);
        if (deviceCache != null && deviceCache.getGmtOnline() != null) {
            deviceCorrelationInfoVO.setGmtOnline(DateUtils.parseDateToStr(new Date(Long.parseLong(deviceCache.getGmtOnline()))));
        }
        return deviceCorrelationInfoVO;
    }

    /**
     * 批量获取设备详情
     *
     * @param productKey
     * @param deviceNames
     * @return com.bsj.travel.def.global.JsonResult
     * @author ljx
     * @time 2024/4/8 14:28
     */
    @Override
    public List<DeviceCorrelationInfoVO> batchQueryDeviceDetail(String productKey, List<String> deviceNames) {
        List<DeviceCorrelationInfoVO> deviceCorrelationInfoVOS = new LinkedList<>();
        String tableName = Constants.DEVICE_TAB_STR + productKey;
        List<DeviceDO> deviceDOS = deviceMapper.selectByDeviceNames(tableName, deviceNames);
        if (CollectionUtils.isEmpty(deviceDOS)) {
            throw new ApiException(JsonResultEnum.DEVICE_IS_NOT_EXIST);
        }
        ProductDO productDO = redisCached.hgetProductInfo(productKey);
        String productName = productDO.getProductName();
        String nodesType = productDO.getNodesType();
        Integer authMethod = productDO.getAuthMethod();
        for (DeviceDO deviceDO : deviceDOS) {
            String deviceName = deviceDO.getDeviceName();
            Date gmtActive = deviceDO.getGmtActive();
            DeviceCorrelationInfoVO deviceCorrelationInfoVO = new DeviceCorrelationInfoVO();
            BeanUtils.copyProperties(deviceDO, deviceCorrelationInfoVO);
            deviceCorrelationInfoVO.setProductName(productName);
            deviceCorrelationInfoVO.setProductKey(productKey);
            deviceCorrelationInfoVO.setNodesType(nodesType);
            deviceCorrelationInfoVO.setAuthMethod(authMethod);
            deviceCorrelationInfoVO.setGmtCreate(DateUtils.parseDateToStr(deviceDO.getGmtCreate()));
            if (gmtActive == null) {
                deviceCorrelationInfoVO.setStatus(NumericalConstants.ONE);
            } else {
                deviceCorrelationInfoVO.setStatus(redisCached.getDeviceStatus(deviceName + "&" + productKey));
                deviceCorrelationInfoVO.setGmtActive(DateUtils.parseDateToStr(gmtActive));
            }
            DeviceDO deviceCache = redisCached.hgetDeviceInfo(productKey, deviceName);
            if (deviceCache != null && deviceCache.getGmtOnline() != null) {
                deviceCorrelationInfoVO.setGmtOnline(DateUtils.parseDateToStr(new Date(Long.parseLong(deviceCache.getGmtOnline()))));
            }
            deviceCorrelationInfoVOS.add(deviceCorrelationInfoVO);
        }
        return deviceCorrelationInfoVOS;
    }

    /**
     * 消息发布
     *
     * @param deviceOpenApiMsgReportDTO
     * @return com.bsj.travel.def.global.JsonResult
     * @author ljx
     * @time 2024/4/8 15:06
     */
    @Override
    public JsonResult pub(DeviceOpenApiMsgReportDTO deviceOpenApiMsgReportDTO) {
        String productKey = deviceOpenApiMsgReportDTO.getProductKey();
        String topicClass = deviceOpenApiMsgReportDTO.getTopicClass();
        ProductDO productDO = redisCached.hgetProductInfo(productKey);
        QueryWrapper<ProductTopicDO> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("topicClass", com.bsj.travel.util.StringUtils.reductionTopicClass(topicClass, productKey, deviceOpenApiMsgReportDTO.getDeviceName()));
        queryWrapper.eq("productId", productDO.getProductId());
        ProductTopicDO productTopicDO = productTopicMapper.selectOne(queryWrapper);
        if (productTopicDO == null) {
            return JsonResult.fail(JsonResultEnum.TOPIC_IS_NOT_EXIST);
        }
        MqttEventPublish mqttEventPublish = new MqttEventPublish();
        mqttEventPublish.setClient(deviceOpenApiMsgReportDTO.getDeviceName() + Constants.AND_SYMBOL + productKey);
        mqttEventPublish.setTopic(topicClass);
        mqttEventPublish.setQos(deviceOpenApiMsgReportDTO.getQos());
        mqttEventPublish.setData(deviceOpenApiMsgReportDTO.getContent());
        baseMqPushUtil.sendMqttMsgDownTag(mqttEventPublish);
        return JsonResult.success();
    }

    /**
     * 数据校验
     *
     * @param tableName
     * @param devices
     * @return com.bsj.travel.def.global.JsonResult
     * @author ljx
     * @time 2024/4/8 11:25
     */
    private String checkImportData(String tableName, List<DeviceOpenApiDTO> devices) {
        Map<String, String> deviceNameMap = new HashMap<>();
        List<String> deviceNames = devices.stream().map(DeviceOpenApiDTO::getDeviceName).collect(Collectors.toList());
        List<String> deviceNameList = deviceMapper.selectDeviceNameByDeviceNames(tableName, deviceNames);
        StringBuilder errorInfo = new StringBuilder();
        int num = NumericalConstants.ONE;
        for (DeviceOpenApiDTO deviceTemplateVO : devices) {
            String deviceName = deviceTemplateVO.getDeviceName();
            if (StringUtils.isBlank(deviceName)) {
                errorInfo.append("第" + num + "组设备号不能为空;");
            } else {
                if (!deviceName.matches(RegularExpressionConstants.NUMBERS_AND_LETTERS)) {
                    errorInfo.append("第" + num + "组设备号格式错误;");
                }
                if (deviceNameList.contains(deviceName)) {
                    errorInfo.append("第" + num + "组设备号已存在;");
                }
                if (deviceNameMap.get(deviceName) != null) {
                    errorInfo.append("第" + num + "组设备号在该批设备中已存在;");
                }
                deviceNameMap.put(deviceName, deviceName);
            }
            num++;
        }
        return errorInfo.toString();
    }

    /**
     * 参数校验
     *
     * @param deviceOpenApiDTO
     * @return void
     * @author ljx
     * @time 2024/1/15 15:55
     */
    private void paramCheck(DeviceOpenApiDTO deviceOpenApiDTO) {
        //设备号格式校验
        String deviceName = deviceOpenApiDTO.getDeviceName();
        if (!deviceName.matches(RegularExpressionConstants.NUMBERS_AND_LETTERS)) {
            throw new ApiException(JsonResultEnum.DEVICE_NAME_FORMAT_ERROR);
        }
        //去重校验
        DeviceSearchDTO deviceSearchDTO = new DeviceSearchDTO();
        deviceSearchDTO.setProductKey(Constants.DEVICE_TAB_STR + deviceOpenApiDTO.getProductKey());
        deviceSearchDTO.setDeviceName(deviceName);
        if (deviceMapper.countByCondition(deviceSearchDTO) > NumericalConstants.ZERO) {
            throw new ApiException(JsonResultEnum.DEVICE_NAME_IS_NOT_NULL);
        }
    }

    /**
     * 发送消息
     *
     * @param deviceDO
     * @return void
     * @author ljx
     * @time 2024/3/1 10:21
     */
    private void sent(DeviceDO deviceDO, String productKey) {
        Rp<DeviceInfoVO> rp = new Rp<>();
        rp.setTag(BaseServerTopicConstants.DEVICE_LIFE_CYCLE_CHANGE_TAG);
        DeviceInfoVO deviceInfoVO = new DeviceInfoVO();
        BeanUtils.copyProperties(deviceDO, deviceInfoVO);
        rp.setData(deviceInfoVO);
        rp.setProductKey(productKey);
        baseMqPushUtil.sendTopicBytes(BaseServerTopicConstants.PRODUCT_SUBSCRIBE_TOPIC, BaseServerTopicConstants.DEVICE_LIFE_CYCLE_CHANGE_TAG, JSON.toJSONBytes(rp));
    }

}
